#include <boost/static_assert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/foreach.hpp>
#include <boost/type_traits.hpp>

#include <GL/glut.h>
#include <unistd.h>
#include <string>


#include "config.h"
#include "matrix.h"
#include "primitive.h"
#include "integer.h"
#include "predicate_2d.h"
#include "predicate_3d.h"
#include "mesh.h"
#include "hierarchy_mesh.h"
#include "aabbtree.h"

#include "random.h"
#include "particle.h"

#define GLFLOAT(VAL) VAL
typedef float value_type;
typedef math::mesh_traits <value_type, 3>::FaceType            face_type ;
typedef math::HierarchyMesh<math::mesh_traits<value_type, 3> >   mesh_type ;
typedef math::AABBTreePoly <value_type, 3>                   AABBTreePolyType; 
typedef math::Polygon<value_type, 3>                         polygon_type ;
typedef math::vector<value_type, 3>                          point3 ;
typedef math::vector<value_type, 2>                          point2 ;
typedef math::vector<value_type, 3>                          vector3 ;
typedef math::vector<value_type, 2>                          vector2 ;
typedef vector3 color_type ;




value_type off_x = 0.0;

int WINDOW_WIDTH = 900, WINDOW_HEIGHT = 600;

// mouse state
int prevX = 0, prevY = 0;
bool leftPressed = false, rightPressed = false, middlePressed = false;

// view state
float rotMat [ 16 ] = {1,0,0,0,
					   0,1,0,0,
					   0,0,1,0,
					   0,0,0,1};
float focus [ 3 ] = {0,0,-3.5};

bool gb_light = false; 


typedef math::random_generator<value_type, math::uniform_tag, -1, 1> random_type ;
typedef math::pbm::system <math::pbm::particle_system> bounce_type ;
typedef math::pbm::system_state <math::vector <value_type, 3>, math::pbm::particle_system> state_type ;

mesh_type mesh; 
const unsigned num_particle = 10000;
bounce_type  bounce[num_particle]; 
state_type initial_state;
random_type generator ;

void reshape(int width, int height)
{
	glViewport(0, 0, width, height);
	WINDOW_WIDTH = width;
	WINDOW_HEIGHT = height;
}

	void init_light(void)
	{
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_NORMALIZE);

		// Light model parameters:
		// -------------------------------------------

		GLfloat lmKa[] = {0.0, 0.0, 0.0, 0.0 };
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmKa);

		glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0);
		glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0.0);

		// -------------------------------------------
		// Spotlight Attenuation

		GLfloat spot_direction[] = {1.0, -1.0, -1.0 };
		GLint spot_exponent = 30;
		GLint spot_cutoff = 180;

		glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
		glLighti(GL_LIGHT0, GL_SPOT_EXPONENT, spot_exponent);
		glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, spot_cutoff);

		GLfloat Kc = 1.0;
		GLfloat Kl = 0.0;
		GLfloat Kq = 0.0;

		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION,Kc);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, Kl);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, Kq);


		// ------------------------------------------- 
		// Lighting parameters:

		GLfloat light_pos[] = {0.0f, 5.0f, 5.0f, 1.0f};
                
		GLfloat light_Ka[]  = {0.8f, .8f, .8f, 1.0f};
		GLfloat light_Kd[]  = {.5f, .3f, 0.9f, 1.0f};
		GLfloat light_Ks[]  = {.8f, .8f, .8f, 1.0f};

		//GLfloat light_Ka[]  = {1.0f, 0.5f, 0.5f, 1.0f};
		//GLfloat light_Kd[]  = {1.f, 0.1f, 0.1f, 1.0f};
		//GLfloat light_Ks[]  = {1.0f, 1.0f, 1.0f, 1.0f};

		glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_Ka);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_Kd);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_Ks);

		// -------------------------------------------
		// Material parameters:

		//GLfloat material_Ka[] = {0.5f, 0.0f, 0.0f, 1.0f};
		//GLfloat material_Kd[] = {0.4f, 0.4f, 0.5f, 1.0f};
		//GLfloat material_Ks[] = {0.8f, 0.8f, 0.0f, 1.0f};
		GLfloat material_Ka[] = {.5f, .5f, .5f, 1.0f};
		GLfloat material_Kd[] = {0.4f, 0.4f, 0.4f, 1.0f};
		GLfloat material_Ks[] = {0.8f, 0.8f, 0.8f, 1.0f};
		GLfloat material_Ke[] = {0.1f, 0.1f, 0.1f, 0.0f};
		GLfloat material_Se = 20.0f;

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_Ka);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_Kd);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_Ks);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_Ke);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material_Se);

	}

void init(void)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

	glClearColor(0, 0, 0, 1);   


	GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat mat_shininess[] = { 50.0 };
//	GLfloat light_position[] = { 0.0, 0.0, 0.0, 1.0 };
	glClearColor (0.0, 0.0, 0.0, 0.0);
	glShadeModel (GL_SMOOTH);

	init_light();
/*
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
*/
//	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
}

void draw_triangles_facet (const std::vector<point3 *> & point, const vector3 & normal, int faceid	,	std::vector<int> pv)
{
	point3 p (0.0f, 0.0f, 0.0f);
	glBegin(GL_POLYGON);
	glNormal3f(GLFLOAT(normal[0]), GLFLOAT(normal[1]), GLFLOAT(normal[2])); 
	for (unsigned i = 0; i < point.size(); ++i)
	{
		p += (*point[i]) ;
		glVertex3f( GLFLOAT((*point[i])[0]), GLFLOAT((*point[i])[1]), GLFLOAT((*point[i])[2]));
	}

	p /= value_type(point.size());
	glEnd();
}

void draw_mesh_facet (mesh_type * mesh)
{
//	glColor3f(1.0, 1.0, 1.0);
	for (unsigned i = 0; i < mesh->faces.size(); ++i)
	{
		std::vector<point3 *> p;
		mesh->faces[i]->Face2VertexCoord(p); 
		std::vector<int> pv;
		mesh->faces[i]->Face2VertexIdx(pv);
		draw_triangles_facet ( p, mesh->faces[i]->normal(), i, pv); 
	}	
}

std::vector<unsigned > v_face_id; 

point3 initial_position (0.0, 0.0, -.5); 
inline void particle_generator (bounce_type & particle)
{
	initial_state.position = initial_position ;
	float x = generator.random() ;
	float y = generator.random() ;
	float z = generator.random() ; 
	initial_state.velocity = x + initial_state.direction.x
		, y + initial_state.direction.y , z + initial_state.direction.z; 
	color_type color ((x + 1.0 ) / 2.0, (y + 1.0) /2.0, (z+1.0)/2.0);  

	particle.initialize(initial_state, 0.01, generator.random() * 75, color);	
} 

void draw_particles (bounce_type * particle)
{

	glBegin(GL_LINES);
	glLineWidth(3.0);
	for (unsigned i = 0; i < num_particle ; ++i)
	{
		if (particle[i].is_live())
		{
			const vector3 & p1 = particle[i].get_oldstate().position ;
			const vector3 & p2 = particle[i].get_state().position ;
			const color_type & c = particle[i].get_color(); 
			glColor4f (c[0], c[1], c[2], 0.5); 
			glVertex3f(p1.x, p1.y, p1.z);
			glVertex3f(p2.x, p2.y, p2.z);
		}
		else
		{
			particle_generator(bounce[i]); 
		}
	}
	glEnd(); 
}
void draw_intersect_face (const mesh_type & mesh, const std::vector<unsigned> & v_face_id)
{
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor3f (1.0, 0, 0);
	glLineWidth(4.0);
	for (unsigned i = 0; i < v_face_id.size(); ++i)
	{
		std::vector<point3 *> p;
		mesh.faces[v_face_id[i]]->Face2VertexCoord(p); 
		std::vector<int> pv;
		mesh.faces[v_face_id[i]]->Face2VertexIdx(pv);
		draw_triangles_facet ( p, mesh.faces[i]->normal(), i, pv); 
	}		
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor3f (1.0, 1.0, 1.0);
	glLineWidth(1.0);
}
void display() 
{
	if (gb_light)
	{
		glEnable(GL_LIGHT0);
		glEnable(GL_LIGHTING);
		//glEnable(GL_DEPTH_TEST);	
//		glEnable(GL_CULL_FACE);
//		glCullFace(GL_FRONT);
	}
	else
	{
		glDisable(GL_LIGHT0);
		glDisable(GL_LIGHTING);
		glEnable(GL_CULL_FACE);
//		glCullFace(GL_FRONT);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	// Set up viewing matrices
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60, float(WINDOW_WIDTH)/WINDOW_HEIGHT, .0001, 100);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glPushMatrix();


	//Camera



	glTranslatef(focus[0], focus[1], focus[2]);
	glMultMatrixf(rotMat);
	glRotatef(-90, 1, 0, 0);


	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);
	glEnable(GL_CULL_FACE);

//			glEnable(GL_BLEND);
//			glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

			glColor4f(0.1,0.1,0.75,0.5);

	draw_mesh_facet (& mesh);

	// drawing code goes here
	//glutSolidCube ( 1 );

	glDisable(GL_LIGHT0);
	glDisable(GL_LIGHTING);
//	glDisable(GL_CULL_FACE);

			glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
			glEnable(GL_BLEND);
			glColor4f(0.1,0.1,0.75,0.5);
	draw_particles(bounce); 
	glDisable(GL_BLEND);




//			glDisable(GL_BLEND);
	glPopMatrix();
//	glFlush ();
	glutSwapBuffers();
}


void mouse(int button, int state, int x, int y)
{
	y = WINDOW_HEIGHT - y;

	// Mouse state that should always be stored on pressing
	if (state == GLUT_DOWN)
	{
		prevX = x;
		prevY = y;
	}

	if (button == GLUT_LEFT_BUTTON)
	{
		leftPressed = state == GLUT_DOWN;
	}

	if (button == GLUT_RIGHT_BUTTON)
	{
		rightPressed = state == GLUT_DOWN;
	}
	
	if (button == GLUT_MIDDLE_BUTTON)
	{
		middlePressed = state == GLUT_DOWN;
	}
}


void idle()
{
	for (unsigned i = 0 ; i < num_particle ; ++i )
	{
		bounce[i].run();
	}
	glutPostRedisplay(); 
}
void motion(int x, int y)
{
	y = WINDOW_HEIGHT - y;

	float dx = (x - prevX);
	float dy = (y - prevY);

	// rotate the scene
	if (leftPressed)
	{
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glRotatef(dx, 0, 1, 0);
		glRotatef(dy, -1, 0, 0);
		glMultMatrixf(rotMat);
		glGetFloatv(GL_MODELVIEW_MATRIX, rotMat);
	}
	else if (middlePressed)
	{
		focus [ 0 ] += 0.005 * dx;
		focus [ 1 ] += 0.005 * dy;
	}
	else if (rightPressed)
	{
		focus [ 2 ] += 0.01 * dy;
	}
	
	// Store previous mouse positions
	prevX = x;
	prevY = y;

	glutPostRedisplay();
}


void keyboard(unsigned char key, int x, int y)
{
	//if (key == 'f' || key == 'F') frame = !frame ; 
	//if (key == 'b' || key == 'B') gb_number = !gb_number ;
	//if (key == 'v' || key == 'V') gb_vertex = !gb_vertex; 
	if (key == 'v' || key == 'V') gb_light = !gb_light; 

	if (key == 'u' || key == 'U') 
	{
		initial_position += vector3 (0.0, 0.0, 0.05); 
	}

	if (key == 'd' || key == 'D') 
	{
		initial_position -= vector3 (0.0, 0.0, 0.05); 
	}

	if (key == 'l' || key == 'L') 
	{
		initial_position -= vector3 (0.05, 0.0, 0.0); 
	}

	if (key == 'r' || key == 'R') 
	{
		initial_position += vector3 (0.05, 0.0, 0.0); 
	}
	glutPostRedisplay();
}


int main(int argc, char * argv[])
{
	try
	{
		std::string filename ;

		std::cin>>filename; 
		std::ifstream ifs (filename.c_str());
		ifs>>mesh; 
		mesh.rotateX (1.414);
		mesh.build_hierarchy(); 
		std::cin>>initial_state.velocity; 
		std::cin>>initial_state.position ;
		std::cin>>initial_state.direction ;
 
		value_type time_step ;
		std::cin>>time_step ;

		time_step = 0.01; 
		generator.seed(5);
		for (unsigned i = 0; i < num_particle ; ++i)
		{
			bounce[i].set_object(mesh);
			particle_generator(bounce[i]); 
		}
	}
	catch (const char errormsg)
	{
		std::cout<<errormsg<<std::endl;
	}

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutCreateWindow("Particle System.");

	init();

	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutIdleFunc(idle);

	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	// main loop
	glutMainLoop();


	return 0;
}
